﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using TallComponents.Web.Pdf.Resources;
using System.Web.UI.HtmlControls;

[assembly: WebResource(TallComponents.Web.Pdf.PdfViewer.ScriptResourcePath, "text/javascript")]
[assembly: WebResource(TallComponents.Web.Pdf.PdfViewer.CursorPan1Path, "image/x-win-bitmap")]
[assembly: WebResource(TallComponents.Web.Pdf.PdfViewer.CursorPan2Path, "image/x-win-bitmap")]
[assembly: WebResource(TallComponents.Web.Pdf.PdfViewer.CursorZoomInPath, "image/x-win-bitmap")]
[assembly: WebResource(TallComponents.Web.Pdf.PdfViewer.CursorZoomOutPath, "image/x-win-bitmap")]
[assembly: WebResource(TallComponents.Web.Pdf.PdfViewer.StyleSheetPath, "text/css", PerformSubstitution = true)]

namespace TallComponents.Web.Pdf
{
   /// <summary>
   /// Displays a (partial) preview of page from a PDF document.
   /// </summary>
   /// <remarks>
   /// This control renders an interactive preview of a page from a PDF document. Users can interact with the
   /// preview using mouse to pan and zoom. 
   /// <para>A <see cref="PdfViewerToolbar"/> can be used to control the viewer behaviour, browse through pages and provide feedback about the
   /// current page.</para>
   /// </remarks>
   [System.Drawing.ToolboxBitmap(typeof(PdfViewer), IconPath)]
   [ToolboxItem("System.Web.UI.Design.WebControlToolboxItem, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
   public class PdfViewer : ScriptControl
   {
      private const int DefaultVerticalSpacing = 20;
      private const int DefaultHorisontalSpacing = 20;

      internal const string IconPath = "TallComponents.Web.Pdf.PdfWebViewer.ico";

      internal const string CursorPan1Path = "TallComponents.Web.Pdf.pan1.cur";
      internal const string CursorPan2Path = "TallComponents.Web.Pdf.pan2.cur";
      internal const string CursorZoomInPath = "TallComponents.Web.Pdf.zoomin.cur";
      internal const string CursorZoomOutPath = "TallComponents.Web.Pdf.zoomout.cur";
      internal const string StyleSheetPath = "TallComponents.Web.Pdf.style.css";

      // This is the resource path to the javascript source file
      // Note that resource paths always start with the assembly default namespace
#if DEBUG
      internal const string ScriptResourcePath = "TallComponents.Web.Pdf.PdfViewer.js";
#else
      internal const string ScriptResourcePath = "TallComponents.Web.Pdf.PdfViewer.min.js";
#endif

      internal const string ClientControlType = "TallComponents.PdfViewer";

      /// <summary>
      /// Initializes a new instance of the <see cref="PdfViewer"/> class.
      /// </summary>
      public PdfViewer()
      {
         BackColor = System.Drawing.Color.FromArgb(238, 238, 238); // light grey
         Width = Unit.Pixel(400);
         Height = Unit.Pixel(400);
         ZoomFactor = 0.75;
         PageLayout = PageLayout.Single;
         VerticalSpacing = DefaultVerticalSpacing;
         HorisontalSpacing = DefaultHorisontalSpacing;
      }

      /// <summary>
      /// Gets or sets the index of the page to display.
      /// </summary>
      /// <value>The index of the page to display.</value>
      [DefaultValue(0)]
      public int PageIndex
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets the initial page layout.
      /// </summary>
      [DefaultValue(PageLayout.Single)]
      public PageLayout PageLayout { get; set; }

      /// <summary>
      /// Gets or sets the vertical space (in points) between 2 pages.
      /// </summary>
      [DefaultValue(DefaultVerticalSpacing)]
      public int VerticalSpacing { get; set; }

      /// <summary>
      /// Gets or sets the horisontal space (in points) between 2 pages.
      /// </summary>
      [DefaultValue(DefaultHorisontalSpacing)]
      public int HorisontalSpacing { get; set; }

      /// <summary>
      /// Gets or sets the initial zoom factor.
      /// </summary>
      /// <remarks>This value is only used when <see cref="ZoomMode"/> is set to <c>ZoomFactor</c>.</remarks>
      /// <value>The zoom factor.</value>
      /// <seealso cref="ZoomMode"/>
      [DefaultValue(0.75)]
      public double ZoomFactor
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets the initial zoom mode 
      /// </summary>
      /// <remarks>Set to <c>FitToWidth</c> or <c>FitToPage</c> to make the viewer initially zoom to the page with or to fit the entire page in the viewer. If
      /// set to <c>ZoomFactor</c> (default value) the zoom factor specified by the <see cref="ZoomFactor"/> property is applied.
      /// </remarks>
      /// <value>The zoom mode.</value>
      public ZoomMode ZoomMode { get; set; }

      /// <summary>
      /// Gets or sets the unique key for the document.
      /// </summary>
      /// <remarks>
      /// This is used to retrieve the document from storage.
      /// </remarks>
      /// <value>The unique key for the document.</value>
      public string StorageKey
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets the CSS class applied to the rectangle that highlights search results.
      /// </summary>
      /// <value>The CSS class applied to the rectangle that highlights search results.</value>
      [DefaultValue("selection")]
      public string SearchResultCssClass { get; set; }

      /// <summary>
      /// Gets or sets the URL for the PDF rendering service.
      /// </summary>
      /// <value>The service URL.</value>
      [UrlProperty]
      [Editor("System.Web.UI.Design.UrlEditor", typeof(System.Drawing.Design.UITypeEditor))]
      public string ServiceUrl { get; set; }

      /// <summary>
      /// Gets or sets the URL for the service that handles full text searching.
      /// </summary>
      /// <value>The search service URL.</value>
      [UrlProperty]
      [Editor("System.Web.UI.Design.UrlEditor", typeof(System.Drawing.Design.UITypeEditor))]
      public string SearchServiceUrl { get; set; }

      /// <summary>
      /// Raises the <see cref="E:PreRender"/> event.
      /// </summary>
      /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
      protected override void OnPreRender(EventArgs e)
      {
         base.OnPreRender(e);
         RenderCssReference();
      }

      private void RenderCssReference()
      {
         string cssUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), StyleSheetPath);
         HtmlLink link = new HtmlLink();
         link.Href = cssUrl;
         link.Attributes.Add("type", "text/css");
         link.Attributes.Add("rel", "stylesheet");
         Page.Header.Controls.Add(link);
      }


      /// <summary>
      /// Outputs the markup for this control.
      /// </summary>
      /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that 
      /// receives the server control content.</param>
      protected override void Render(HtmlTextWriter writer)
      {
         if (string.IsNullOrEmpty(ServiceUrl))
            throw new HttpException("ServiceUrl not set.");

         // outer div
         AddAttributesToRender(writer);
         writer.AddStyleAttribute(HtmlTextWriterStyle.Overflow, "auto");
         writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative");
         writer.RenderBeginTag(HtmlTextWriterTag.Div);

         // background div
         if (ControlStyleCreated)
         {
            bool renderBackgroundDiv = false;
            var styles = ControlStyle.GetStyleAttributes(this);
            foreach (string key in styles.Keys)
            {
               switch (key.ToLower())
               {
                  case "background":
                  case "background-color":
                  case "background-image":
                  case "background-position":
                  case "background-attachment":
                  case "background-repeat":
                     renderBackgroundDiv = true;
                     writer.AddStyleAttribute(key, styles[key]);
                     break;
                  default:
                     break;
               }
            }

            if (renderBackgroundDiv)
            {
               writer.AddStyleAttribute(HtmlTextWriterStyle.ZIndex, "0");
               writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute");
               writer.AddStyleAttribute(HtmlTextWriterStyle.Left, "0px");
               writer.AddStyleAttribute(HtmlTextWriterStyle.Top, "0px");
               writer.AddStyleAttribute(HtmlTextWriterStyle.Width, Width.ToString());
               writer.AddStyleAttribute(HtmlTextWriterStyle.Height, Height.ToString());
               writer.RenderBeginTag(HtmlTextWriterTag.Div);
               writer.RenderEndTag();
            }
         }

         // Preview div
         writer.AddStyleAttribute(HtmlTextWriterStyle.ZIndex, "10");
         writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute");
         writer.AddStyleAttribute(HtmlTextWriterStyle.Width, Width.ToString());
         writer.AddStyleAttribute(HtmlTextWriterStyle.Height, Height.ToString());
         writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, string.Format("url({0}), move", HttpUtility.HtmlAttributeEncode(Page.ClientScript.GetWebResourceUrl(typeof(PdfViewer), CursorPan1Path))));
         writer.AddStyleAttribute(HtmlTextWriterStyle.Left, "0px");
         writer.AddStyleAttribute(HtmlTextWriterStyle.Top, "0px");
         // Script control is hooked up to inner div...
         writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);
         writer.RenderBeginTag(HtmlTextWriterTag.Div);
         writer.RenderEndTag(); // div id=pageImage
         writer.RenderEndTag(); // div id=outer

         if (!DesignMode)
         {
            ScriptManager.RegisterScriptDescriptors(this);
         }
      }

      /// <summary>
      /// Adds HTML attributes and styles that need to be rendered to the specified <see cref="T:System.Web.UI.HtmlTextWriterTag"/>. 
      /// </summary>
      /// <remarks>This override will only render ControlStyle, Title and Attributes.</remarks>
      /// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output 
      /// stream to render HTML content on the client.</param>
      protected override void AddAttributesToRender(HtmlTextWriter writer)
      {
         if (null != ToolTip)
            writer.AddAttribute(HtmlTextWriterAttribute.Title, ToolTip);

         if (ControlStyleCreated && !ControlStyle.IsEmpty)
         {
            var styles = ControlStyle.GetStyleAttributes(this);
            foreach (string key in styles.Keys)
            {
               switch (key.ToLower())
               {
                  case "width":
                  case "height":
                  case "border-style":
                  case "border-width":
                  case "border-color":
                     writer.AddStyleAttribute(key, styles[key]);
                     break;
                  default:
                     break; // ignore
               }
            }
         }

         foreach (string attribute in Attributes.Keys)
         {
            switch (attribute.ToLower())
            {
               case "title":
               case "class":
                  writer.AddAttribute(attribute, Attributes[attribute]);
                  break;
               default:
                  break; // ignore
            }
         }
      }

      /// <summary>
      /// Gets the PDF storage.
      /// </summary>
      /// <value>The PDF storage.</value>
      private TallComponents.Web.Storage.IPdfStorage Storage
      {
         get
         {
            if (_storage == null)
            {
               _storage = TallComponents.Web.Storage.PdfStorage.Provider;
            }
            return _storage;
         }
      }

      /// <summary>
      /// Gets the script manager for the current request.
      /// </summary>
      /// <value>The script manager for the current request.</value>
      private ScriptManager ScriptManager
      {
         get
         {
            if (_scriptManager == null)
            {
               if (Page == null)
               {
                  throw new InvalidOperationException(Text.Common_PageCannotBeNull);
               }
               _scriptManager = ScriptManager.GetCurrent(Page);
               if (_scriptManager == null)
               {
                  throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Text.Common_ScriptManagerRequired, ID));
               }
            }
            return _scriptManager;
         }
      }


      /// <summary>
      /// Returns the <see cref="T:System.Web.UI.ScriptDescriptor"/> objects for the control.
      /// </summary>
      /// <returns>
      /// An <see cref="T:System.Collections.IEnumerable"/> collection of <see cref="T:System.Web.UI.ScriptDescriptor"/> objects.
      /// </returns>
      protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
      {
         var desc = new ScriptControlDescriptor(ClientControlType, ClientID);
         desc.AddProperty("pageIndex", PageIndex);
         desc.AddProperty("pageLayout", PageLayout.ToString().ToLower());
         desc.AddProperty("verticalSpacing", VerticalSpacing);
         desc.AddProperty("horisontalSpacing", HorisontalSpacing);
         if (ZoomMode != ZoomMode.ZoomFactor)
         {
            desc.AddProperty("zoom", ZoomMode.ToString());
         }
         else
         {
            desc.AddProperty("zoom", ZoomFactor);
         }

         desc.AddProperty("zz_serviceUrl", ServiceUrl);
         desc.AddProperty("searchServiceUrl", SearchServiceUrl);
         if (!String.IsNullOrEmpty(SearchResultCssClass))
         {
            desc.AddProperty("searchResultCssClass", SearchResultCssClass);
         }

         desc.AddProperty("document", Storage.GetDocument(StorageKey));
         desc.AddProperty("cursors", new cursors
         {
            panUp = Page.ClientScript.GetWebResourceUrl(typeof(PdfViewer), CursorPan1Path),
            panDown = Page.ClientScript.GetWebResourceUrl(typeof(PdfViewer), CursorPan2Path),
            zoomIn = Page.ClientScript.GetWebResourceUrl(typeof(PdfViewer), CursorZoomInPath),
            zoomOut = Page.ClientScript.GetWebResourceUrl(typeof(PdfViewer), CursorZoomOutPath)
         });

         yield return desc;
      }

      private class cursors
      {
         [Obfuscation]
         public string panUp { get; set; }
         [Obfuscation]
         public string panDown { get; set; }
         [Obfuscation]
         public string zoomIn { get; set; }
         [Obfuscation]
         public string zoomOut { get; set; }
      }

      /// <summary>
      /// Returns the script files for the control.
      /// </summary>
      /// <returns>
      /// An <see cref="T:System.Collections.IEnumerable"/> collection that contains ECMAScript (JavaScript) files that have been registered as embedded resources.
      /// </returns>
      protected override IEnumerable<ScriptReference> GetScriptReferences()
      {
         var scriptReferences = new List<ScriptReference> {
            new ScriptReference( ScriptResourcePath, "TallComponents.Web.PdfViewer" )
         };
         return scriptReferences;
      }

      private TallComponents.Web.Storage.IPdfStorage _storage;
      private ScriptManager _scriptManager;
   }
}